1//////////////////////////////////////////////////////////////////////
  2// LibFile: miscellaneous.scad
  3//   Miscellaneous modules that didn't fit in anywhere else, including
  4//   bounding box, chain hull, extrusions, and minkowski based
  5//   modules.  
  6// Includes:
  7//   include <BOSL2/std.scad>
  8// FileGroup: Basic Modeling
  9// FileSummary: Extrusion, bounding box, chain hull and minkowski-based transforms.
 10// FileFootnotes: STD=Included in std.scad
 11//////////////////////////////////////////////////////////////////////
 12
 13// Section: Extrusion
 14
 15// Module: extrude_from_to()
 16// Synopsis: Extrudes 2D children between two points in 3D space.
 17// SynTags: Geom
 18// Topics: Extrusion, Miscellaneous
 19// See Also: path_sweep(), path_extrude2d()
 20// Usage:
 21//   extrude_from_to(pt1, pt2, [convexity=], [twist=], [scale=], [slices=]) 2D-CHILDREN;
 22// Description:
 23//   Extrudes the 2D children linearly between the 3d points pt1 and pt2.  The origin of the 2D children are placed on
 24//   pt1 and pt2, and oriented perpendicular to the line between the points.  
 25// Arguments:
 26//   pt1 = starting point of extrusion.
 27//   pt2 = ending point of extrusion.
 28//   ---
 29//   convexity = max number of times a line could intersect a wall of the 2D shape being extruded.
 30//   twist = number of degrees to twist the 2D shape over the entire extrusion length.
 31//   scale = scale multiplier for end of extrusion compared the start.
 32//   slices = Number of slices along the extrusion to break the extrusion into.  Useful for refining `twist` extrusions.
 33// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
 34//   extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
 35//       xcopies(3) circle(3, $fn=32);
 36//   }
 37module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
 38    req_children($children);
 39    check =
 40      assert(is_vector(pt1),"First point must be a vector")
 41      assert(is_vector(pt2),"Second point must be a vector");
 42    pt1 = point3d(pt1);
 43    pt2 = point3d(pt2);
 44    rtp = xyz_to_spherical(pt2-pt1);
 45    attachable()
 46    {
 47      translate(pt1) {
 48          rotate([0, rtp[2], rtp[1]]) {
 49              if (rtp[0] > 0) {
 50                  linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
 51                      children();
 52                  }
 53              }
 54          }
 55      }
 56      union();
 57    }
 58}
 59
 60
 61// Module: path_extrude2d()
 62// Synopsis: Extrudes 2D children along a 2D path.
 63// SynTags: Geom
 64// Topics: Miscellaneous, Extrusion 
 65// See Also: path_sweep(), path_extrude()
 66// Usage:
 67//   path_extrude2d(path, [caps=], [closed=], [s=], [convexity=]) 2D-CHILDREN;
 68// Description:
 69//   Extrudes 2D children along the given 2D path, with optional rounded endcaps.
 70//   It works by constructing straight sections corresponding to each segment of the path and inserting rounded joints at each corner.
 71//   If the children are symmetric across the Y axis line then you can set caps=true to produce rounded caps on the ends of the profile.
 72//   If you set caps to true for asymmetric children then incorrect caps will be generated.
 73// Arguments:
 74//   path = The 2D path to extrude the geometry along.
 75//   ---
 76//   caps = If true, caps each end of the path with a rounded copy of the children.  Children must by symmetric across the Y axis, or results are wrong.  Default: false
 77//   closed = If true, connect the starting point of the path to the ending point.  Default: false
 78//   convexity = The max number of times a line could pass though a wall.  Default: 10
 79//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, it messes with centering your view.  Default: The length of the diagonal of the path's bounding box.
 80// Example:
 81//   path = [
 82//       each right(50, p=arc(d=100,angle=[90,180])),
 83//       each left(50, p=arc(d=100,angle=[0,-90])),
 84//   ];
 85//   path_extrude2d(path,caps=false) {
 86//       fwd(2.5) square([5,6],center=true);
 87//       fwd(6) square([10,5],center=true);
 88//   }
 89// Example:
 90//   path_extrude2d(arc(d=100,angle=[180,270]),caps=true)
 91//       trapezoid(w1=10, w2=5, h=10, anchor=BACK);
 92// Example:
 93//   include <BOSL2/beziers.scad>
 94//   path = bezpath_curve([
 95//       [-50,0], [-25,50], [0,0], [50,0]
 96//   ]);
 97//   path_extrude2d(path, caps=false)
 98//       trapezoid(w1=10, w2=3, h=5, anchor=BACK);
 99// Example: Un-Closed Path
100//   $fn=16;
101//   spath = star(id=15,od=35,n=5);
102//   path_extrude2d(spath, caps=false, closed=false)
103//       move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]])
104//           circle(r=1.5);
105// Example: Complex Endcaps
106//   $fn=16;
107//   spath = star(id=15,od=35,n=5);
108//   path_extrude2d(spath, caps=true, closed=false)
109//       move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]])
110//           circle(r=1.5);
111module path_extrude2d(path, caps=false, closed=false, s, convexity=10) {
112    req_children($children);
113    extra_ang = 0.1; // Extra angle for overlap of joints
114    check =
115       assert(caps==false || closed==false, "Cannot have caps on a closed extrusion")
116       assert(is_path(path,2));
117    path = deduplicate(path);
118    s = s!=undef? s :
119        let(b = pointlist_bounds(path))
120        norm(b[1]-b[0]);
121    check2 = assert(is_finite(s));
122    L = len(path);
123    attachable(){
124      union(){
125        for (i = [0:1:L-(closed?1:2)]) {
126            seg = select(path, i, i+1);
127            segv = seg[1] - seg[0];
128            seglen = norm(segv);
129            translate((seg[0]+seg[1])/2) {
130                rot(from=BACK, to=segv) {
131                    difference() {
132                        xrot(90) {
133                            linear_extrude(height=seglen, center=true, convexity=convexity) {
134                                children();
135                            }
136                        }
137                        if (closed || i>0) {
138                            pt = select(path, i-1);
139                            pang = v_theta(rot(from=-segv, to=RIGHT, p=pt - seg[0]));
140                            fwd(seglen/2+0.01) zrot(pang/2) cube(s, anchor=BACK);
141                        }
142                        if (closed || i<L-2) {
143                            pt = select(path, i+2);
144                            pang = v_theta(rot(from=segv, to=RIGHT, p=pt - seg[1]));
145                            back(seglen/2+0.01) zrot(pang/2) cube(s, anchor=FWD);
146                        }
147                    }
148                }
149            }
150        }
151        for (t=triplet(path,wrap=closed)) {
152            ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
153            delt = point3d(t[2] - t[1]);
154            if (ang!=0)
155                translate(t[1]) {
156                    frame_map(y=delt, z=UP)
157                        rotate(-sign(ang)*extra_ang/2)
158                            rotate_extrude(angle=ang+sign(ang)*extra_ang)
159                                if (ang<0)
160                                    right_half(planar=true) children();
161                                else
162                                    left_half(planar=true) children();                          
163                }
164                    
165        }
166        if (caps) {
167            bseg = select(path,0,1);
168            move(bseg[0])
169                rot(from=BACK, to=bseg[0]-bseg[1])
170                    rotate_extrude(angle=180)
171                        right_half(planar=true) children();
172            eseg = select(path,-2,-1);
173            move(eseg[1])
174                rot(from=BACK, to=eseg[1]-eseg[0])
175                    rotate_extrude(angle=180)
176                        right_half(planar=true) children();
177        }
178      }
179      union();
180    }
181}
182
183
184// Module: path_extrude()
185// Synopsis: Extrudes 2D children along a 3D path.
186// SynTags: Geom
187// Topics: Paths, Extrusion, Miscellaneous
188// See Also: path_sweep(), path_extrude2d()
189// Usage:
190//   path_extrude(path, [convexity], [clipsize]) 2D-CHILDREN;
191// Description:
192//   Extrudes 2D children along a 3D path.  This may be slow and can have problems with twisting.  
193// Arguments:
194//   path = Array of points for the bezier path to extrude along.
195//   convexity = Maximum number of walls a ray can pass through.
196//   clipsize = Increase if artifacts are left.  Default: 100
197// Example(FlatSpin,VPD=600,VPT=[75,16,20]):
198//   path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
199//   path_extrude(path) circle(r=10, $fn=6);
200module path_extrude(path, convexity=10, clipsize=100) {
201    req_children($children);
202    rotmats = cumprod([
203       for (i = idx(path,e=-2)) let(
204           vec1 = i==0? UP : unit(path[i]-path[i-1], UP),
205           vec2 = unit(path[i+1]-path[i], UP)
206       ) rot(from=vec1,to=vec2)
207    ]);
208    // This adds a rotation midway between each item on the list
209    interp = rot_resample(rotmats,n=2,method="count");
210    epsilon = 0.0001;  // Make segments ever so slightly too long so they overlap.
211    ptcount = len(path);
212    attachable(){
213       for (i = [0:1:ptcount-2]) {
214           pt1 = path[i];
215           pt2 = path[i+1];
216           dist = norm(pt2-pt1);
217           T = rotmats[i];
218           difference() {
219               translate(pt1) {
220                   multmatrix(T) {
221                       down(clipsize/2/2) {
222                           if ((dist+clipsize/2) > 0) {
223                               linear_extrude(height=dist+clipsize/2, convexity=convexity) {
224                                   children();
225                               }
226                           }
227                       }
228                   }
229               }
230               translate(pt1) {
231                   hq = (i > 0)? interp[2*i-1] : T;
232                   multmatrix(hq) down(clipsize/2+epsilon) cube(clipsize, center=true);
233               }
234               translate(pt2) {
235                   hq = (i < ptcount-2)? interp[2*i+1] : T;
236                   multmatrix(hq) up(clipsize/2+epsilon) cube(clipsize, center=true);
237               }
238           }
239       }
240       union();
241    }
242}
243
244
245
246// Module: cylindrical_extrude()
247// Synopsis: Extrudes 2D children outwards around a cylinder.
248// SynTags: Geom
249// Topics: Miscellaneous, Extrusion, Rotation
250// See Also: heightfield(), cylindrical_heightfield(), cyl()
251// Usage:
252//   cylindrical_extrude(ir|id=, or|od=, [size=], [convexity=], [spin=], [orient=]) 2D-CHILDREN;
253// Description:
254//   Extrudes its 2D children outwards, curved around a cylindrical shape.  Uses $fn/$fa/$fs to
255//   control the faceting of the extrusion.  
256// Arguments:
257//   ir = The inner radius to extrude from.
258//   or = The outer radius to extrude to.
259//   ---
260//   od = The outer diameter to extrude to.
261//   id = The inner diameter to extrude from.
262//   size = The [X,Y] size of the 2D children to extrude.  Default: [1000,1000]
263//   convexity = The max number of times a line could pass though a wall.  Default: 10
264//   spin = Amount in degrees to spin around cylindrical axis.  Default: 0
265//   orient = The orientation of the cylinder to wrap around, given as a vector.  Default: UP
266// Example:
267//   cylindrical_extrude(or=50, ir=45)
268//       text(text="Hello World!", size=10, halign="center", valign="center");
269// Example: Spin Around the Cylindrical Axis
270//   cylindrical_extrude(or=50, ir=45, spin=90)
271//       text(text="Hello World!", size=10, halign="center", valign="center");
272// Example: Orient to the Y Axis.
273//   cylindrical_extrude(or=40, ir=35, orient=BACK)
274//       text(text="Hello World!", size=10, halign="center", valign="center");
275module cylindrical_extrude(ir, or, od, id, size=1000, convexity=10, spin=0, orient=UP) {
276    req_children($children);
277    check1 = assert(is_num(size) || is_vector(size,2));
278    size = is_num(size)? [size,size] : size;
279    ir = get_radius(r=ir,d=id);
280    or = get_radius(r=or,d=od);
281    check2 = assert(all_positive([ir,or]), "Must supply positive inner and outer radius or diameter");
282    index_r = or;
283    circumf = 2 * PI * index_r;
284    width = min(size.x, circumf);
285    check3 = assert(width <= circumf, "Shape would more than completely wrap around.");
286    sides = segs(or);
287    step = circumf / sides;
288    steps = ceil(width / step);
289    attachable() {
290      rot(from=UP, to=orient) rot(spin) {
291          for (i=[0:1:steps-2]) {
292              x = (i+0.5-steps/2) * step;
293              zrot(360 * x / circumf) {
294                  fwd(or*cos(180/sides)) {
295                      xrot(-90) {
296                          linear_extrude(height=or-ir, scale=[ir/or,1], center=false, convexity=convexity) {
297                              yflip()
298                              intersection() {
299                                  left(x) children();
300                                  rect([quantup(step,pow(2,-15)),size.y]);
301                              }
302                          }
303                      }
304                  }
305              }
306          }
307      }
308      union();
309    }
310}
311
312
313
314//////////////////////////////////////////////////////////////////////
315// Section: Bounding Box
316//////////////////////////////////////////////////////////////////////
317
318// Module: bounding_box()
319// Synopsis: Creates the smallest bounding box that contains all the children.
320// SynTags: Geom
321// Topics: Miscellaneous, Bounds, Bounding Boxes
322// See Also: pointlist_bounds()
323// Usage:
324//   bounding_box([excess],[planar]) CHILDREN;
325// Description:
326//   Returns the smallest axis-aligned square (or cube) shape that contains all the 2D (or 3D)
327//   children given.  The module children() must 3d when planar=false and
328//   2d when planar=true, or you will get a warning of mixing dimension
329//   or scaling by 0.
330// Arguments:
331//   excess = The amount that the bounding box should be larger than needed to bound the children, in each axis.
332//   planar = If true, creates a 2D bounding rectangle.  Is false, creates a 3D bounding cube.  Default: false
333// Example(3D):
334//   module shapes() {
335//       translate([10,8,4]) cube(5);
336//       translate([3,0,12]) cube(2);
337//   }
338//   #bounding_box() shapes();
339//   shapes();
340// Example(2D):
341//   module shapes() {
342//       translate([10,8]) square(5);
343//       translate([3,0]) square(2);
344//   }
345//   #bounding_box(planar=true) shapes();
346//   shapes();
347module bounding_box(excess=0, planar=false) {
348    // a 3d (or 2d when planar=true) approx. of the children projection on X axis
349    module _xProjection() {
350        if (planar) {
351            projection()
352                rotate([90,0,0])
353                    linear_extrude(1, center=true)
354                        hull()
355                            children();
356        } else {
357            xs = excess<.1? 1: excess;
358            linear_extrude(xs, center=true)
359                projection()
360                    rotate([90,0,0])
361                        linear_extrude(xs, center=true)
362                            projection()
363                                hull()
364                                    children();
365        }
366    }
367
368    // a bounding box with an offset of 1 in all axis
369    module _oversize_bbox() {
370        if (planar) {
371            minkowski() {
372                _xProjection() children(); // x axis
373                rotate(-90) _xProjection() rotate(90) children(); // y axis
374            }
375        } else {
376            minkowski() {
377                _xProjection() children(); // x axis
378                rotate(-90) _xProjection() rotate(90) children(); // y axis
379                rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis
380            }
381        }
382    }
383
384    // offsets a cube by `excess`
385    module _shrink_cube() {
386        intersection() {
387            translate((1-excess)*[ 1, 1, 1]) children();
388            translate((1-excess)*[-1,-1,-1]) children();
389        }
390    }
391
392    req_children($children);
393    attachable(){
394      if(planar) {
395          offset(excess-1/2) _oversize_bbox() children();
396      } else {
397          render(convexity=2)
398          if (excess>.1) {
399              _oversize_bbox() children();
400          } else {
401              _shrink_cube() _oversize_bbox() children();
402          }
403      }
404      union();
405    }
406}
407
408
409//////////////////////////////////////////////////////////////////////
410// Section: Hull Based Modules
411//////////////////////////////////////////////////////////////////////
412
413
414// Module: chain_hull()
415// Synopsis: Performs the union of hull operations between consecutive pairs of children.
416// SynTags: Geom
417// Topics: Miscellaneous
418// See Also: hull()
419// Usage:
420//   chain_hull() CHILDREN;
421//
422// Description:
423//   Performs hull operations between consecutive pairs of children,
424//   then unions all of the hull results.  This can be a very slow
425//   operation, but it can provide results that are hard to get
426//   otherwise.
427//
428// Side Effects:
429//   `$idx` is set to the index value of the first child of each hulling pair, and can be used to modify each child pair individually.
430//   `$primary` is set to true when the child is the first in a chain pair.
431//
432// Example:
433//   chain_hull() {
434//       cube(5, center=true);
435//       translate([30, 0, 0]) sphere(d=15);
436//       translate([60, 30, 0]) cylinder(d=10, h=20);
437//       translate([60, 60, 0]) cube([10,1,20], center=false);
438//   }
439// Example: Using `$idx` and `$primary`
440//   chain_hull() {
441//       zrot(  0) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
442//       zrot( 45) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
443//       zrot( 90) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
444//       zrot(135) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
445//       zrot(180) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
446//   }
447module chain_hull()
448{
449    req_children($children);
450    attachable(){
451        if ($children == 1) {
452            children();
453        }
454        else {
455            for (i =[1:1:$children-1]) {
456                $idx = i;
457                hull() {
458                    let($primary=true) children(i-1);
459                    let($primary=false) children(i);
460                }
461            }
462        }
463        union();
464    }
465}
466
467
468
469//////////////////////////////////////////////////////////////////////
470// Section: Minkowski and 3D Offset
471//////////////////////////////////////////////////////////////////////
472
473// Module: minkowski_difference()
474// Synopsis: Removes diff shapes from base shape surface.
475// SynTags: Geom
476// Topics: Miscellaneous
477// See Also: offset3d()
478// Usage:
479//   minkowski_difference() { BASE; DIFF1; DIFF2; ... }
480// Description:
481//   Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the
482//   surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the
483//   surface of its base shape.
484// Arguments:
485//   planar = If true, performs minkowski difference in 2D.  Default: false (3D)
486// Example:
487//   minkowski_difference() {
488//       union() {
489//           cube([120,70,70], center=true);
490//           cube([70,120,70], center=true);
491//           cube([70,70,120], center=true);
492//       }
493//       sphere(r=10);
494//   }
495module minkowski_difference(planar=false) {
496    req_children($children);
497    attachable(){
498         difference() {
499             bounding_box(excess=0, planar=planar) children(0);
500             render(convexity=20) {
501                 minkowski() {
502                     difference() {
503                         bounding_box(excess=1, planar=planar) children(0);
504                         children(0);
505                     }
506                     for (i=[1:1:$children-1]) children(i);
507                 }
508             }
509         }
510         union();
511    }
512}
513
514
515
516
517// Module: offset3d()
518// Synopsis: Expands or contracts the surface of a 3D object.
519// SynTags: Geom
520// Topics: Miscellaneous
521// See Also: minkowski_difference(), round3d()
522// Usage:
523//   offset3d(r, [size], [convexity]) CHILDREN;
524// Description:
525//   Expands or contracts the surface of a 3D object by a given amount.  This is very, very slow.
526//   No really, this is unbearably slow.  It uses `minkowski()`.  Use this as a last resort.
527//   This is so slow that no example images will be rendered.
528// Arguments:
529//   r = Radius to expand object by.  Negative numbers contract the object. 
530//   size = Maximum size of object to be contracted, given as a scalar.  Default: 100
531//   convexity = Max number of times a line could intersect the walls of the object.  Default: 10
532module offset3d(r, size=100, convexity=10) {
533    req_children($children);
534    n = quant(max(8,segs(abs(r))),4);
535    attachable(){
536      if (r==0) {
537          children();
538      } else if (r>0) {
539          render(convexity=convexity)
540          minkowski() {
541              children();
542              sphere(r, $fn=n);
543          }
544      } else {
545          size2 = size * [1,1,1];
546          size1 = size2 * 1.02;
547          render(convexity=convexity)
548          difference() {
549              cube(size2, center=true);
550              minkowski() {
551                  difference() {
552                      cube(size1, center=true);
553                      children();
554                  }
555                  sphere(-r, $fn=n);
556              }
557          }
558      }
559      union();
560    }
561}
562
563
564// Module: round3d()
565// Synopsis: Rounds arbitrary 3d objects.
566// SynTags: Geom
567// Topics: Rounding, Miscellaneous
568// See Also: offset3d(), minkowski_difference()
569// Usage:
570//   round3d(r) CHILDREN;
571//   round3d(or) CHILDREN;
572//   round3d(ir) CHILDREN;
573//   round3d(or, ir) CHILDREN;
574// Description:
575//   Rounds arbitrary 3D objects.  Giving `r` rounds all concave and convex corners.  Giving just `ir`
576//   rounds just concave corners.  Giving just `or` rounds convex corners.  Giving both `ir` and `or`
577//   can let you round to different radii for concave and convex corners.  The 3D object must not have
578//   any parts narrower than twice the `or` radius.  Such parts will disappear.  This is an *extremely*
579//   slow operation.  I cannot emphasize enough just how slow it is.  It uses `minkowski()` multiple times.
580//   Use this as a last resort.  This is so slow that no example images will be rendered.
581// Arguments:
582//   r = Radius to round all concave and convex corners to.
583//   or = Radius to round only outside (convex) corners to.  Use instead of `r`.
584//   ir = Radius to round only inside (concave) corners to.  Use instead of `r`.
585module round3d(r, or, ir, size=100)
586{
587    req_children($children);
588    or = get_radius(r1=or, r=r, dflt=0);
589    ir = get_radius(r1=ir, r=r, dflt=0);
590    attachable(){
591       offset3d(or, size=size)
592           offset3d(-ir-or, size=size)
593               offset3d(ir, size=size)
594                   children();
595       union();
596    }
597}
598
599
600
601// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap